using System;
using System.Collections.Generic;

namespace Tycho.Parser.Peg {
    public delegate Yield Yield();

    public abstract class Production : IProduction {
        public virtual string Name { get; set; }
        public bool IsIncludedInErrors { get; protected set; }
        public IErrorInformation ErrorInformation { get; protected set; }
        private Dictionary<int, ParseResult> _memoTable;
        private char[] MemoTableSource;

        public Func<ParseResult, ParseResult> ResultTransform;

        public bool IsCommon;

        protected Production () : this (null) {
        }

        protected Production (Func<ParseResult, ParseResult> resultTransform) {
            ResultTransform = resultTransform;
            Name = null;
            IsIncludedInErrors = true;
            MemoTableSource = null;
        }

        public Dictionary<int, ParseResult> MemoTable {
            get { return _memoTable; }
        }

        public Dictionary<int, ParseResult> GetMemoTable (char [] source) {
            SetupMemoTable (source);
            return _memoTable;
        }

        public virtual bool IsInfix {
            get {
                return false;
            }
        }

        public virtual bool IsLeftRecursive {
            get {
                return false;
            }
        }

        public virtual IInfixInformation InfixInformation {
            get { return new NotInfixInformation (); }
        }

        public ParseResult Parse (string source, int index, ParseContext context) {
            ParseResult result = null;
            Func<ParseResult, Yield> continuation = r =>
            {
                result = r;
                return null;
            };
            Yield yield = Parse(source.ToCharArray(), index, context, source, new ParseEnvironment(null), continuation);
            ExecuteYields(yield);
            return result;
        }

        private string DebugName
        {
            get
            {
                if (Name == null)
                {
                    return GetHashCode().ToString();
                } else
                {
                    return Name;
                }
            }
        }

        public ParseResult Parse(char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment)
        {
            ParseResult result = null;
            Func<ParseResult, Yield> continuation = r =>
            {
                result = r;
                return null;
            };
            Yield yield = Parse(source, index, context, sourceString, parseEnvironment, continuation);
            ExecuteYields(yield);
            return result;
        }

        void ExecuteYields(Yield yield)
        {
            while (yield != null)
            {
                yield = yield();
            }
        }

        public Yield Parse(char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment, Func<ParseResult, Yield> continuation)
        {
            ParseResult result;

            SetupMemoTable (source);

            if (!GetMemo (index, out result)) {
                BeforeParseLeftRecursionSetup (index, parseEnvironment);

                return ParseAndMemoise (source, index, context, sourceString, parseEnvironment, res =>
                                                                                                    {
                                                                                                        return AfterParseLeftRecursionHandling(source, index, parseEnvironment, context, sourceString, res, continuation);
                                                                                                    });
            } else if (result == parseEnvironment.LeftRecursionInformation.RecursiveResult) {
                parseEnvironment.LeftRecursionInformation.DeclareRecursiveParse (this, index);
                return continuation(null);
            } else if (parseEnvironment.SyntaxErrorRecoverer.Recovering) {
                return continuation(null);
            } else if (result == parseEnvironment.SyntaxErrors.ErrorResult) {
                SyntaxErrorRecoveryException recoveryException = parseEnvironment.SyntaxErrorRecoverer.Recover (source, index, context, sourceString, parseEnvironment, this);

                _memoTable[index] = recoveryException.Result;
                throw recoveryException;
            }

            return continuation(result);
        }

        protected virtual Yield AfterParseLeftRecursionHandling (char[] source, int index, ParseEnvironment parseEnvironment, ParseContext context, string sourceString, ParseResult result, Func<ParseResult, Yield> continuation) {
            if (parseEnvironment.LeftRecursionInformation.IsRecursiveProduction (this, index)) {
                return GrowParse (result, source, index, context, sourceString, parseEnvironment, continuation);
            } else {
                return continuation (result);
            }
        }

        protected virtual void BeforeParseLeftRecursionSetup (int index, ParseEnvironment parseEnvironment) {
            _memoTable[index] = parseEnvironment.LeftRecursionInformation.RecursiveResult;
        }

        private void SetupMemoTable (char[] source) {
            if (MemoTableSource != source) {
                _memoTable = new Dictionary<int, ParseResult> ();
                MemoTableSource = source;
            }
        }

        private bool GetMemo (int index, out ParseResult result) {
            return _memoTable.TryGetValue (index, out result);
        }

        private Yield GrowParse (ParseResult result, char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment, Func<ParseResult, Yield> continuation) {
            parseEnvironment.LeftRecursionInformation.ClearInvolvedProductions(source, index);

            return ParseAndMemoise(source, index, context, sourceString, parseEnvironment, latestResult =>
                                                                                               {
                                                                                                   if (latestResult != null && result.Index < latestResult.Index)
                                                                                                   {
                                                                                                       return () => GrowParse(latestResult, source, index, context, sourceString, parseEnvironment, continuation);
                                                                                                   }
                                                                                                   else
                                                                                                   {
                                                                                                       _memoTable[index] = result;
                                                                                                       parseEnvironment.LeftRecursionInformation.CompletedRecursiveParse(index);
                                                                                                       return continuation(result);
                                                                                                   }
                                                                                               });
        }

        private Yield ParseAndMemoise (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment, Func<ParseResult, Yield> continuation) {
            parseEnvironment.LeftRecursionInformation.BeginParsing (this, index);

            return ParseAndInvokeAction (context, index, source, sourceString, parseEnvironment, result =>
                                                                                                     {
                                                                                                         parseEnvironment.LeftRecursionInformation.EndParsing();

                                                                                                         if (result != null
                                                                                                             && result.Index <= index)
                                                                                                         {
                                                                                                             _memoTable.Remove(index);
                                                                                                         }
                                                                                                         else
                                                                                                         {
                                                                                                             _memoTable[index] = result;
                                                                                                         }

                                                                                                         return continuation (result);
                                                                                                     });
        }

        private Yield ParseAndInvokeAction (ParseContext context, int index, char[] source, string sourceString, ParseEnvironment parseEnvironment, Func<ParseResult, Yield> continuation) {
            return ReallyParse (source, index, context, sourceString, parseEnvironment, result =>
                                                                                                          {
                                                                                                              if (result == null)
                                                                                                              {
                                                                                                                  parseEnvironment.SyntaxErrors.LogSyntaxError(index, this, context, () => parseEnvironment.LeftRecursionInformation.ProductionInvocationStack);
                                                                                                              }
                                                                                                              else
                                                                                                              {
                                                                                                                  if (ResultTransform != null)
                                                                                                                  {
                                                                                                                      result = ResultTransform(result);
                                                                                                                  }
                                                                                                              }

                                                                                                              return continuation(result);
                                                                                                          });
        }

        protected virtual ParseResult ReallyParse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment)
        {
            ParseResult result = null;
            Func<ParseResult, Yield> continuation = r =>
                                                        {
                                                            result = r;
                                                            return null;
                                                        };
            Yield yield = ReallyParse(source, index, context, sourceString, parseEnvironment, continuation);
            ExecuteYields(yield);
            return result;
        }

        protected virtual Yield ReallyParse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment, Func<ParseResult, Yield> continuation)
        {
            return () => continuation(ReallyParse(source, index, context, sourceString, parseEnvironment));
        }
    }
}